package com.frank.oj.manager;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.frank.model.entity.problem.*;
import com.frank.oj.common.exception.StatusFailException;
import com.frank.oj.common.exception.StatusSystemErrorException;
import com.frank.oj.common.result.ResultStatus;
import com.frank.oj.constant.Constants;
import com.frank.oj.exception.ProblemIDRepeatException;
import com.frank.oj.model.dto.ProblemDTO;
import com.frank.oj.model.vo.ImportProblemVO;
import com.frank.oj.service.entity.common.TagEntityService;
import com.frank.oj.service.entity.problem.LanguageEntityService;
import com.frank.oj.service.entity.problem.ProblemCaseEntityService;
import com.frank.oj.service.entity.problem.ProblemEntityService;
import com.frank.oj.shiro.AccountProfile;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.*;

/**
 * @author frank
 * @Date 2024/4/5
 */
@Component
@Slf4j(topic = "hcoj")
public class ProblemFileManager {
    @Resource
    private LanguageEntityService languageEntityService;

    @Resource
    private ProblemEntityService problemEntityService;

    @Resource
    private ProblemCaseEntityService problemCaseEntityService;

    @Resource
    private TagEntityService tagEntityService;

    public void importProblem(MultipartFile file) throws StatusSystemErrorException, StatusFailException {
        // 获取文件后缀
        String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1);
        if (!"zip".toUpperCase().contains(suffix.toUpperCase())) {
            throw new RuntimeException("文件格式错误，仅支持zip格式");
        }

        String fileDirId = IdUtil.simpleUUID();
        String fileDir = Constants.File.TESTCASE_TMP_FOLDER.getPath() + File.separator + fileDirId;
        String filePath = fileDir + File.separator + file.getOriginalFilename();

        FileUtil.mkdir(fileDir);
        try {
            file.transferTo(new File(filePath));
        } catch (IOException e) {
            FileUtil.del(fileDir);
            throw new StatusSystemErrorException("服务器异常：文件上传失败");
        }
        //解压缩
        ZipUtil.unzip(filePath, fileDir);
        // 删除压缩包
        FileUtil.del(filePath);

        File testCaseFileList = new File(fileDir);
        File[] files = testCaseFileList.listFiles();
        if (files == null || files.length == 0) {
            FileUtil.del(fileDir);
            throw new StatusFailException("该文件夹下没有文件");
        }

        HashMap<String, File> problemInfo = new HashMap<>();
        HashMap<String, File> testcaseInfo = new HashMap<>();

        for (File tmp : files) {
            if (tmp.isFile()) {
                if (!tmp.getName().endsWith("json")) { //json类型
                    FileUtil.del(fileDir);
                    throw new StatusFailException("编号为【" + tmp.getName() + "】的文件格式错误，请使用json文件！");
                }
                String tmpPreName = tmp.getName().substring(0, tmp.getName().lastIndexOf("."));
                problemInfo.put(tmpPreName, tmp);
            }
            if (tmp.isDirectory()) { //文件
                testcaseInfo.put(tmp.getName(), tmp);
            }
        }

        //    读取json文件
        HashMap<String, ImportProblemVO> problemVOMap = new HashMap<>();

        for (String key : problemInfo.keySet()) {
            if (testcaseInfo.getOrDefault(key, null) == null) {
                FileUtil.del(fileDir);
                throw new StatusFailException("请检查编号为【" + key + "】的题目数据文件与测试数据文件夹是否对应");
            }

            try {
                FileReader fileReader = new FileReader(problemInfo.get(key));
                ImportProblemVO importProblemVO = JSONUtil.toBean(fileReader.readString(), ImportProblemVO.class);
                problemVOMap.put(key, importProblemVO);
            } catch (Exception e) {
                FileUtil.del(fileDir);
                throw new StatusFailException("请检查编号为【" + key + "】的题目json文件的格式" + e.getLocalizedMessage());

            }
        }

        // 支持的语言
        LambdaQueryWrapper<Language> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Language::getOj, "ME");
        List<Language> languageList = languageEntityService.list(queryWrapper);

        HashMap<String, Long> languageMap = new HashMap<>();
        for (Language language : languageList) {
            languageMap.put(language.getName(), language.getId());
        }

        // 支持的标签
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        LinkedList<ProblemDTO> problemDTOS = new LinkedList<>();
        LambdaQueryWrapper<Tag> tagLambdaQueryWrapper = new LambdaQueryWrapper<>();
        tagLambdaQueryWrapper.eq(Tag::getOj, "ME");
        List<Tag> tagList = tagEntityService.list(tagLambdaQueryWrapper);

        HashMap<String, Tag> tagMap = new HashMap<>();
        for (Tag tag : tagList) {
            tagMap.put(tag.getName().toUpperCase(), tag);
        }

        for (String key : problemInfo.keySet()) {
            ImportProblemVO importProblemVO = problemVOMap.get(key);
            LinkedList<Language> languages = new LinkedList<>();
            for (String language : importProblemVO.getLanguages()) {
                Long lid = languageMap.getOrDefault(language, null);

                if (lid == null) {
                    throw new StatusFailException("请检查编号为【" + key + "】的题目json文件中的编程语言是否正确");
                }
                languages.add(new Language().setId(lid).setName(language));
            }

            List<CodeTemplate> codeTemplates = new LinkedList<>();
            for (Map<String, String> codeTemplate : importProblemVO.getCodeTemplates()) {
                String language = codeTemplate.getOrDefault("language", null);
                String code = codeTemplate.getOrDefault("code", null);
                Long lid = languageMap.getOrDefault(language, null);
                if (language == null || code == null || lid == null) {
                    FileUtil.del(fileDir);
                    throw new StatusFailException("请检查编号为【" + key + "】的题目json文件中的编程语言是否正确");
                }
                codeTemplates.add(new CodeTemplate().setCode(code).setStatus(true).setLid(lid));
            }

            LinkedList<Tag> tags = new LinkedList<>();
            for (String tagStr : importProblemVO.getTags()) {
                Tag tag = tagMap.getOrDefault(tagStr.toUpperCase(), null);
                if (tag == null) { //数据库中不存在
                    tags.add(new Tag().setName(tagStr).setOj("ME"));
                } else {
                    tags.add(tag);
                }
            }

            Problem problem = BeanUtil.mapToBean(importProblemVO.getProblem(), Problem.class, true);
            if (problem.getAuthor() == null) {
                problem.setAuthor(userRole.getUsername());
            }
            problem.setIsGroup(false);
            LinkedList<ProblemCase> problemCases = new LinkedList<>();
            for (HashMap<String, Object> tmp : importProblemVO.getSamples()) {
                problemCases.add(BeanUtil.mapToBean(tmp, ProblemCase.class, true));
            }

            if (importProblemVO.getUserExtraFile() != null) {
                JSONObject userExtraFileJson = JSONUtil.parseObj(importProblemVO.getUserExtraFile());
                problem.setUserExtraFile(userExtraFileJson.toString());
            }
            if (importProblemVO.getJudgeExtraFile() != null) {
                JSONObject judgeExtraFileJson = JSONUtil.parseObj(importProblemVO.getJudgeExtraFile());
                problem.setJudgeExtraFile(judgeExtraFileJson.toString());
            }

            ProblemDTO problemDTO = new ProblemDTO();
            problemDTO.setProblem(problem)
                    .setCodeTemplates(codeTemplates)
                    .setTags(tags)
                    .setLanguages(languages)
                    .setUploadTestcaseDir(fileDir + File.separator + key)
                    .setIsUploadTestCase(true)
                    .setSamples(problemCases);

            Constants.JudgeMode judgeMode = Constants.JudgeMode.getJudgeMode(importProblemVO.getJudgeMode());

            if (judgeMode == null) {
                problemDTO.setJudgeMode(Constants.JudgeMode.DEFAULT.getMode());
            } else {
                problemDTO.setJudgeMode(judgeMode.getMode());
            }
            problemDTOS.add(problemDTO);
        }

        if (problemDTOS.isEmpty()) {
            throw new StatusFailException("警告：未成功导入题目，请检查文件格式是否正确！");
        } else {
            HashSet<String> repeatProblemIDSet = new HashSet<>();
            HashSet<String> failedProblemIDSet = new HashSet<>();
            int failedCount = 0;
            for (ProblemDTO problemDTO : problemDTOS) {
                try {
                    boolean isOK = problemEntityService.adminAddProblem(problemDTO);
                    if (!isOK) {
                        failedCount++;
                    }
                } catch (ProblemIDRepeatException e) {
                    repeatProblemIDSet.add(problemDTO.getProblem().getProblemId());
                    failedCount++;
                } catch (Exception e) {
                    failedProblemIDSet.add(problemDTO.getProblem().getProblemId());
                    failedCount++;
                }
            }

            if (failedCount > 0) {
                int successCount = problemDTOS.size() - failedCount;
                String errMsg = "[导入结果] 成功数：" + successCount + ",  失败数：" + failedCount +
                        ",  重复失败的题目ID：" + repeatProblemIDSet;
                if (!failedProblemIDSet.isEmpty()) {
                    errMsg = errMsg + "<br/>未知失败的题目ID：" + failedProblemIDSet;
                }

                throw new StatusFailException(errMsg);
            }
        }
    }

    public void exportProblem(List<Long> pidList, HttpServletResponse response) {
        LambdaQueryWrapper<Language> languageQueryWrapper = new LambdaQueryWrapper<>();
        languageQueryWrapper.eq(Language::getOj, "ME");
        List<Language> languageList = languageEntityService.list(languageQueryWrapper);

        HashMap<Long, String> languageMap = new HashMap<>();
        for (Language language : languageList) {
            languageMap.put(language.getId(), language.getName());
        }

        List<Tag> tagList = tagEntityService.list();

        HashMap<Long, String> tagMap = new HashMap<>();
        for (Tag tag : tagList) {
            tagMap.put(tag.getId(), tag.getName());
        }

        String workDir = Constants.File.FILE_DOWNLOAD_TMP_FOLDER.getPath() + File.separator + IdUtil.simpleUUID();
        ExecutorService threadPool = new ThreadPoolExecutor(2, // 核心线程数
                4, // 最大线程数
                3, // 无任务时，线程存活时间
                TimeUnit.SECONDS, // 存活时间单位
                new LinkedBlockingDeque<>(200),  // 阻塞队列
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());// 拒绝策略：队列满是与阻塞队列中第一个任务竞争

        // 存放异步执行的任务
        ArrayList<FutureTask<Void>> futureTasks = new ArrayList<>();

        for (Long pid : pidList) {
            // 为每个pid创建一个新的FutureTask，并将其添加到列表中
            futureTasks.add(new FutureTask<>(new Callable<Void>() {
                @Override
                public Void call() throws Exception {
                    // 根据pid构建测试用例的工作目录路径
                    String testcaseWorkDir = Constants.File.TESTCASE_BASE_FOLDER.getPath() + File.separator + "problem_" + pid;
                    File file = new File(testcaseWorkDir);

                    List<HashMap<String, Object>> problemCases = new LinkedList<>();
                    if (!file.exists() || file.listFiles() == null) { // 测试用例信息（无测试用例）
                        // 从数据库查询对应的测试用例
                        LambdaQueryWrapper<ProblemCase> queryWrapper = new LambdaQueryWrapper<>();
                        queryWrapper.eq(ProblemCase::getPid, pid);
                        List<ProblemCase> problemCaseList = problemCaseEntityService.list(queryWrapper);

                        // 创建测试用例目录
                        FileUtil.mkdir(testcaseWorkDir);

                        // 遍历数据库查询到的测试用例，创建对应的输入输出文件，并转换测试用例信息
                        for (int i = 0; i < problemCaseList.size(); i++) {
                            // 构建输入输出文件路径
                            String filePreName = testcaseWorkDir + File.separator + (i + 1);
                            String inputName = filePreName + ".in";
                            String outputName = filePreName + ".out";

                            // 写入输入输出文件内容
                            FileWriter infileWriter = new FileWriter(inputName);
                            infileWriter.write(problemCaseList.get(i).getInput());
                            FileWriter outfileWriter = new FileWriter(outputName);
                            outfileWriter.write(problemCaseList.get(i).getOutput());

                            // 转换测试用例信息，准备导入
                            ProblemCase problemCase = problemCaseList.get(i)
                                    .setPid(null)
                                    .setInput(inputName)
                                    .setOutput(outputName)
                                    .setGmtCreate(null)
                                    .setStatus(null)
                                    .setId(null)
                                    .setGmtModified(null);
                            HashMap<String, Object> problemCaseMap = new HashMap<>();

                            // 将问题案例对象转换为Map
                            BeanUtil.beanToMap(problemCase, problemCaseMap, false, true);
                            problemCases.add(problemCaseMap);
                        }
                        // 将测试用例目录复制到工作目录
                        FileUtil.copy(testcaseWorkDir, workDir, true);
                    } else {
                        // 如果本地存在测试用例信息文件
                        String infoPath = testcaseWorkDir + File.separator + "info";
                        if (FileUtil.exist(infoPath)) {
                            // 读取并解析测试用例信息文件
                            FileReader reader = new FileReader(infoPath);

                            JSONObject jsonObject = JSONUtil.parseObj(reader.readString());
                            JSONArray testCases = jsonObject.getJSONArray("testCases");
                            for (int i = 0; i < testCases.size(); i++) {
                                JSONObject jsonObject1 = testCases.get(i, JSONObject.class);
                                HashMap<String, Object> problemCaseMap = new HashMap<>();
                                // 从JSON中提取测试用例信息
                                problemCaseMap.put("input", jsonObject1.getStr("inputName"));
                                problemCaseMap.put("output", jsonObject1.getStr("outputName"));
                                Integer score = jsonObject1.getInt("score");
                                Integer groupNum = jsonObject1.getInt("groupNum");
                                // 如果分数大于0，则添加到测试用例信息中
                                if (score != null && score > 0) {
                                    problemCaseMap.put("score", score);
                                }
                                // 如果存在分组编号，则添加到测试用例信息中
                                if (groupNum != null) {
                                    problemCaseMap.put("groupNum", groupNum);
                                }

                                problemCases.add(problemCaseMap);
                            }
                        }
                        // 复制测试用例目录到工作目录
                        FileUtil.copy(testcaseWorkDir, workDir, true);
                    }
                    // 构建并处理问题导出信息
                    ImportProblemVO importProblemVo = problemEntityService.buildExportProblem(pid, problemCases, languageMap, tagMap);
                    String jsonStr = JSONUtil.toJsonStr(importProblemVo);
                    // 将问题信息写入JSON文件
                    FileWriter fileWriter = new FileWriter(workDir + File.separator + "problem_" + pid + ".json");  // 问题描述等信息
                    fileWriter.write(jsonStr);
                    return null;
                }
            }));
        }
        for (FutureTask<Void> futureTask : futureTasks) {
            threadPool.submit(futureTask);
        }
        if (!threadPool.isShutdown()) {
            threadPool.shutdown();
        }

        try {
            threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        } catch (InterruptedException e) {
            log.error("线程池异常：" + e);
        }

        String fileName = "HC_problem_export_" + System.currentTimeMillis() + ".zip";
        ZipUtil.zip(workDir, Constants.File.FILE_DOWNLOAD_TMP_FOLDER.getPath() + File.separator + fileName);

        FileReader fileReader = new FileReader(Constants.File.FILE_DOWNLOAD_TMP_FOLDER.getPath() + File.separator + fileName);
        BufferedInputStream bins = new BufferedInputStream(fileReader.getInputStream());
        OutputStream outs = null;//获取文件输出IO流
        BufferedOutputStream bouts = null;

        try {
            outs = response.getOutputStream();
            bouts = new BufferedOutputStream(outs);
            response.setContentType("application/x-download");
            response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            int bytesRead = 0;
            byte[] buffer = new byte[1024 * 10];
            //开始向网络传输文件流
            while ((bytesRead = bins.read(buffer, 0, 1024 * 10)) != -1) {
                bouts.write(buffer, 0, bytesRead);
            }
            bouts.flush();
        } catch (IOException e) {
            log.error("导出题目数据的压缩文件异常------------>", e);
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, Object> map = new HashMap<>();
            map.put("status", ResultStatus.SYSTEM_ERROR);
            map.put("msg", "导出题目数据失败，请重新尝试！");
            map.put("data", null);

            try {
                response.getWriter().println(JSONUtil.toJsonStr(map));
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        } finally {
            try {
                bins.close();
                if (outs != null) {
                    outs.close();
                }
                if (bouts != null) {
                    bouts.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            // 清空临时文件
            FileUtil.del(workDir);
            FileUtil.del(Constants.File.FILE_DOWNLOAD_TMP_FOLDER.getPath() + File.separator + fileName);
        }
    }
}
